/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { useWorkletCallback } from 'react-native-reanimated';
import { useAdaptiveSheetInternal } from './useAdaptiveSheetInternal';
import {
  ANIMATION_SOURCE, // 动画源枚举（手势、键盘、用户等）
  GESTURE_SOURCE, // 手势源枚举（手柄、滚动视图等）
  SCROLLABLE_TYPE, // 可滚动类型枚举（ScrollView、FlatList等）
} from '../constants';
import type {
  GestureEventsHandlersHookType, // 手势事件处理钩子类型
  GestureEventHandlerCallbackType, // 手势事件处理回调类型
  GestureEventContextType, // 手势事件上下文类型
} from '../types';
import { clamp } from '../utilities/clamp'; // 数值限制工具函数
import { snapPoint } from '../utilities/snapPoint'; // 吸附点计算工具函数

// 导出默认手势事件处理器钩子
export const useGestureEventsHandlersDefault: GestureEventsHandlersHookType = () => {
  const {
    animatedPosition, // 当前动画位置
    animatedSnapPoints, // 吸附点数组
    animatedContainerHeight, // 容器高度
    animatedScrollableType, // 可滚动类型
    animatedHighestSnapPoint, // 最高吸附点
    animatedClosedPosition, // 关闭位置
    animatedScrollableContentOffsetY, // 可滚动内容的Y偏移
    enableOverDrag, // 是否启用过度拖拽
    enablePanDownToClose, // 是否启用向下拖拽关闭
    overDragResistanceFactor, // 过度拖拽阻力系数
    isInTemporaryPosition, // 是否处于临时位置
    isScrollableRefreshable, // 可滚动内容是否可刷新
    animateToPosition, // 动画到指定位置函数
    stopAnimation, // 停止当前动画函数
  } = useAdaptiveSheetInternal();

  // 手势处理函数
  const handleOnStart: GestureEventHandlerCallbackType<GestureEventContextType> = useWorkletCallback(
    function handleOnStart(__, _, context) {
      stopAnimation();

      // 取消当前正在进行的任何动画，确保手势有完全控制权。
      context.initialPosition = animatedPosition.value;

      // 如果滚动内容不在顶部（偏移 > 0），则锁定表单位置。 这确保用户在滚动内容时，表单不会意外移动
      if (animatedScrollableContentOffsetY.value > 0) {
        context.isScrollablePositionLocked = true;
      }
    },
    [stopAnimation, animatedPosition, animatedScrollableContentOffsetY],
  );
  const handleOnActive: GestureEventHandlerCallbackType<GestureEventContextType> = useWorkletCallback(
    function handleOnActive(source, { translationY }, context) {

      let highestSnapPoint = animatedHighestSnapPoint.value;
      if (isInTemporaryPosition.value && typeof context.initialPosition === 'number' && context.initialPosition < highestSnapPoint) {
        highestSnapPoint = context.initialPosition;
      }

      const lowestSnapPoint = enablePanDownToClose ? animatedContainerHeight.value : animatedSnapPoints.value[0];

      if (source === GESTURE_SOURCE.SCROLLABLE && isScrollableRefreshable.value && animatedPosition.value === highestSnapPoint) {
        return;
      }

      const negativeScrollableContentOffset =
        (context.initialPosition === highestSnapPoint && source === GESTURE_SOURCE.SCROLLABLE) || !context.isScrollablePositionLocked
          ? animatedScrollableContentOffsetY.value * -1
          : 0;

      const draggedPosition = context.initialPosition + translationY;

      const accumulatedDraggedPosition = draggedPosition + negativeScrollableContentOffset;

      const clampedPosition = clamp(accumulatedDraggedPosition, highestSnapPoint, lowestSnapPoint);

      if (context.isScrollablePositionLocked && source === GESTURE_SOURCE.SCROLLABLE && animatedPosition.value === highestSnapPoint) {
        context.isScrollablePositionLocked = false;
      }

      if (enableOverDrag) {
        if ((source === GESTURE_SOURCE.HANDLE || animatedScrollableType.value === SCROLLABLE_TYPE.VIEW) && draggedPosition < highestSnapPoint) {
          const resistedPosition = highestSnapPoint - Math.sqrt(1 + (highestSnapPoint - draggedPosition)) * overDragResistanceFactor;
          animatedPosition.value = resistedPosition;
          return;
        }

        if (source === GESTURE_SOURCE.HANDLE && draggedPosition > lowestSnapPoint) {
          const resistedPosition = lowestSnapPoint + Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) * overDragResistanceFactor;
          animatedPosition.value = resistedPosition;
          return;
        }

        if (source === GESTURE_SOURCE.SCROLLABLE && draggedPosition + negativeScrollableContentOffset > lowestSnapPoint) {
          const resistedPosition =
            lowestSnapPoint + Math.sqrt(1 + (draggedPosition + negativeScrollableContentOffset - lowestSnapPoint)) * overDragResistanceFactor;
          animatedPosition.value = resistedPosition;
          return;
        }
      }

      animatedPosition.value = clampedPosition;
    },
    [
      enableOverDrag,
      enablePanDownToClose,
      overDragResistanceFactor,
      isInTemporaryPosition,
      isScrollableRefreshable,
      animatedHighestSnapPoint,
      animatedContainerHeight,
      animatedSnapPoints,
      animatedPosition,
      animatedScrollableType,
      animatedScrollableContentOffsetY,
    ],
  );
  const shouldIgnoreScrollableRefresh = useWorkletCallback(
    (source: GESTURE_SOURCE, isSheetAtHighestSnapPoint: boolean): boolean => {
      'worklet';
      return source === GESTURE_SOURCE.SCROLLABLE && isScrollableRefreshable.value && isSheetAtHighestSnapPoint;
    },
    [isScrollableRefreshable],
  );

  const handleTemporaryPositionSnapBack = useWorkletCallback(
    (context: GestureEventContextType, velocityY: number): boolean => {
      'worklet';
      if (isInTemporaryPosition.value && typeof context.initialPosition === 'number' && context.initialPosition >= animatedPosition.value) {
        if (context.initialPosition > animatedPosition.value) {
          animateToPosition(context.initialPosition, ANIMATION_SOURCE.GESTURE, velocityY / 2);
        }
        return true;
      }
      return false;
    },
    [isInTemporaryPosition, animatedPosition, animateToPosition],
  );

  const calculateNewModeDestination = useWorkletCallback(
    (
      snapPoints: number[],
      translationY: number,
      velocityY: number,
      initialPosition: number,
      containerHeight: number,
      closedPosition: number,
    ): number | undefined => {
      'worklet';
      const minPosition = snapPoints[1];
      const maxPosition = snapPoints[2];
      const isFullScreen = snapPoints[3] === 1;

      if (isFullScreen) {
        return undefined;
      }

      const currentPosition = translationY + initialPosition;
      const dragToCloseThreshold = containerHeight * 0.67;
      const SWIPE_VELOCITY_THRESHOLD = 500;
      const isFastSwipeDown = velocityY > SWIPE_VELOCITY_THRESHOLD;

      if (isFastSwipeDown && currentPosition > initialPosition) {
        return closedPosition;
      }
      if (currentPosition > dragToCloseThreshold) {
        return closedPosition;
      }
      if (currentPosition < maxPosition) {
        return maxPosition;
      }
      if (currentPosition > minPosition) {
        return minPosition;
      }
      return currentPosition;
    },
    [],
  );

  const calculateOldModeDestination = useWorkletCallback(
    (snapPoints: number[], translationY: number, velocityY: number, initialPosition: number, closedPosition: number): number => {
      'worklet';
      const snapPointsCopy = snapPoints.slice();
      if (enablePanDownToClose) {
        snapPointsCopy.unshift(closedPosition);
      }
      return snapPoint(translationY + initialPosition, velocityY, snapPointsCopy);
    },
    [enablePanDownToClose],
  );

  const shouldPreventScrollViewSnap = useWorkletCallback(
    (source: GESTURE_SOURCE, isSheetAtHighestSnapPoint: boolean): boolean => {
      'worklet';
      const wasGestureHandledByScrollView = source === GESTURE_SOURCE.SCROLLABLE && animatedScrollableContentOffsetY.value > 0;
      return wasGestureHandledByScrollView && isSheetAtHighestSnapPoint;
    },
    [animatedScrollableContentOffsetY],
  );

  const handleOnEnd: GestureEventHandlerCallbackType<GestureEventContextType> = useWorkletCallback(
    function handleOnEnd(source, { translationY, velocityY }, context) {
      const highestSnapPoint = animatedHighestSnapPoint.value;
      const isSheetAtHighestSnapPoint = animatedPosition.value === highestSnapPoint;

      if (shouldIgnoreScrollableRefresh(source, isSheetAtHighestSnapPoint)) {
        return;
      }

      if (handleTemporaryPositionSnapBack(context, velocityY)) {
        return;
      }

      if (isInTemporaryPosition.value) {
        isInTemporaryPosition.value = false;
      }

      const snapPoints = animatedSnapPoints.value.slice();
      const isNewMode = snapPoints.length === 4 && (snapPoints[3] === 0 || snapPoints[3] === 1);

      let destinationPoint: number;

      if (isNewMode) {
        const initialPosition = context.initialPosition ?? 0;
        const result = calculateNewModeDestination(
          snapPoints,
          translationY,
          velocityY,
          initialPosition,
          animatedContainerHeight.value,
          animatedClosedPosition.value,
        );
        if (result === undefined) {
          return; 
        }
        destinationPoint = result;
      } else {
        const initialPosition = context.initialPosition ?? 0;
        destinationPoint = calculateOldModeDestination(snapPoints, translationY, velocityY, initialPosition, animatedClosedPosition.value);
      }

      if (destinationPoint === animatedPosition.value) {
        return;
      }

      if (shouldPreventScrollViewSnap(source, isSheetAtHighestSnapPoint)) {
        return;
      }

      animateToPosition(destinationPoint, ANIMATION_SOURCE.GESTURE, velocityY / 2);
    },
    [
      shouldIgnoreScrollableRefresh,
      handleTemporaryPositionSnapBack,
      calculateNewModeDestination,
      calculateOldModeDestination,
      shouldPreventScrollViewSnap,
      isInTemporaryPosition,
      animatedHighestSnapPoint,
      animatedPosition,
      animatedSnapPoints,
      animatedContainerHeight,
      animatedClosedPosition,
      animateToPosition,
    ],
  );

  return {
    handleOnStart,
    handleOnActive,
    handleOnEnd,
  };
};
